package com.hh.flow.facade;

import cn.hutool.core.collection.CollUtil;
import com.hh.common.exception.ServiceException;
import com.hh.common.utils.AssertUtil;
import com.hh.common.utils.uuid.IdUtils;
import com.hh.flow.constant.FlowConstant;
import com.hh.flow.domain.*;
import com.hh.flow.dto.FlowParams;
import com.hh.flow.enums.NodeType;
import com.hh.flow.service.*;
import com.hh.flow.utils.FlowConfigUtil;
import com.hh.flow.dto.FlowCombine;
import com.mybatisflex.core.query.QueryWrapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

import static com.hh.flow.domain.tables.FlowNodeTableDef.flow_node;
import static com.hh.flow.domain.tables.FlowSkipTableDef.flow_skip;

/**
 * @author minliuhua
 * @description: 对外发布工作流的实现类
 * @date: 2023/3/30 15:24
 */
@Service
public class FlowManagerServiceImpl implements FlowManagerService {

    @Resource
    private IFlowInsHisService flowInsHisService;

    @Resource
    private IFlowNodeService nodeService;

    @Resource
    private IFlowSkipService skipService;

    @Resource
    private IFlowInstanceService instanceService;

    @Resource
    private IFlowDefinitionService definitionService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deployFlow() throws Exception {
        FlowCombine combine = FlowConfigUtil.registerConfig();
        // 流程定义
        FlowDefinition definition = combine.getAllDefinition();
        // 所有的流程结点
        List<FlowNode> allNodes = combine.getAllNodes();
        // 所有的流程连线
        List<FlowSkip> allSkips = combine.getAllSkips();
        // 根据不同策略进行新增或者更新
        updateFlow(definition, allNodes, allSkips);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public FlowInstance startFlow(String businessId, FlowParams flowUser) {

        return startFlowAct(Arrays.asList(businessId), flowUser).get(0);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<FlowInstance> startFlow(List<String> businessIds, FlowParams flowUser) {
        return startFlowAct(businessIds, flowUser);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public FlowInstance skipFlow(Long definitionId, String conditionValue, FlowParams flowUser) {
        return skipFlowAct(Arrays.asList(definitionId), conditionValue, null, flowUser).get(0);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<FlowInstance> skipFlow(List<Long> definitionIds, String conditionValue, FlowParams flowUser) {
        return skipFlowAct(definitionIds, conditionValue, null, flowUser);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public FlowInstance skipFlow(Long definitionId, String conditionValue, String message, FlowParams flowUser) {
        return skipFlowAct(Arrays.asList(definitionId), conditionValue, message, flowUser).get(0);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<FlowInstance> skipFlow(List<Long> definitionIds, String conditionValue, String message, FlowParams flowUser) {
        return skipFlowAct(definitionIds, conditionValue, message, flowUser);
    }

    /**
     * 每次只做新增操作,保证新增的flowCode+version是唯一的
     *
     * @param definition
     * @param allNodes
     * @param allSkips
     */
    private void updateFlow(FlowDefinition definition, List<FlowNode> allNodes, List<FlowSkip> allSkips) {
        List<String> flowCodeList = Arrays.asList(definition.getFlowCode());
        List<FlowDefinition> flowDefinitions = definitionService.queryByCodeList(flowCodeList);
        for (int j = 0; j < flowDefinitions.size(); j++) {
            FlowDefinition beforeDefinition = flowDefinitions.get(j);
            if (definition.getFlowCode().equals(beforeDefinition.getFlowCode()) && definition.getVersion().equals(beforeDefinition.getVersion())) {
                throw new ServiceException(definition.getFlowCode() + "(" + definition.getVersion() + ")" + FlowConstant.ALREADY_EXIST);
            }
        }
        // 把之前的流程全部关闭,为开启新的流程做准备
        definitionService.closeFlowByCodeList(flowCodeList);
        definitionService.save(definition);
        nodeService.batchInsert(allNodes);
        skipService.batchInsert(allSkips);
    }

    /**
     * 根据开始的结点,业务id集合开启流程
     *
     * @param businessIds
     * @param flowUser
     * @return
     */
    private List<FlowInstance> startFlowAct(List<String> businessIds, FlowParams flowUser) {
        AssertUtil.isNull(flowUser.getFlowCode(), FlowConstant.NULL_FLOW_CODE);
        AssertUtil.isFalse(CollUtil.isEmpty(businessIds), FlowConstant.NULL_BUSINESS_ID);
        // 根据流程编码获取开启的唯一流程的流程结点集合
        List<FlowNode> nodes = nodeService.queryNewVersionFlowNodeByFlowCode(flowUser.getFlowCode());
        AssertUtil.isFalse(CollUtil.isEmpty(nodes), FlowConstant.NOT_PUBLISH_NODE);
        // 获取开始结点
        FlowNode startNode = getFirstNode(nodes);

        List<FlowInstance> instances = new ArrayList<>();
        List<FlowInsHis> insHisList = new ArrayList<>();
        for (int i = 0; i < businessIds.size(); i++) {
            String businessId = businessIds.get(i);
            AssertUtil.isBlank(businessId, FlowConstant.NULL_BUSINESS_ID);
            // 设置流程实例对象
            FlowInstance instance = setStartInstance(startNode, businessId, flowUser);
            // 设置流程实例历史记录对象
            FlowInsHis insHis = setStartInsHis(startNode, instance, flowUser);
            instances.add(instance);
            insHisList.add(insHis);
        }
        instanceService.batchInsert(instances);
        flowInsHisService.batchInsert(insHisList);
        return instances;
    }

    private List<FlowInstance> skipFlowAct(List<Long> definitionIds, String conditionValue, String message
            , FlowParams flowUser) {
        AssertUtil.isFalse(message != null && message.length() > 500, FlowConstant.MSG_OVER_LENGTH);
        // 获取当前流程
        List<FlowInstance> instances = instanceService.queryByidWithUpdateLock(definitionIds);
        AssertUtil.isFalse(instances == null || instances.size() == 0, FlowConstant.NOT_FOUNT_INSTANCE);
        AssertUtil.isFalse(instances.size() < definitionIds.size(), FlowConstant.LOST_FOUNT_FLOW);
        // 校验这些流程的流程状态是否相同，只有相同的情况下，下面才好做统一处理
        checkSameStatus(instances);
        List<FlowInsHis> insHisList = new ArrayList<>();

        // 第一个结点禁止跳转
        AssertUtil.isFalse(NodeType.start.getKey().equals(instances.get(0).getFlowStatus())
                , FlowConstant.FRIST_FORBID_SKIP);
        // 获取关联的结点
        FlowNode nextNode = getNextNode(instances.get(0), conditionValue, flowUser);
        for (int i = 0; i < instances.size(); i++) {
            FlowInstance instance = instances.get(i);
            // 设置流程实例历史信息
            FlowInsHis insHis = setSkipInsHis(conditionValue, message, instance, nextNode, flowUser);
            insHisList.add(insHis);

            // 设置流程实例信息
            setSkipInstance(nextNode, instance);
        }

        flowInsHisService.batchInsert(insHisList);
        instanceService.batchUpdate(instances);
        return instances;
    }

    /**
     * 设置流程实例信息
     * @param nextNode
     * @param instance
     */
    private void setSkipInstance(FlowNode nextNode, FlowInstance instance) {
        instance.setUpdateTime(new Date());
        instance.setNodeCode(nextNode.getNodeCode());
        instance.setNodeName(nextNode.getNodeName());
        instance.setFlowStatus(nextNode.getNodeType());
    }

    /**
     * 设置流程实例历史信息
     * @param conditionValue
     * @param message
     * @param instance
     * @param nextNode
     * @return
     */
    private FlowInsHis setSkipInsHis(String conditionValue, String message, FlowInstance instance
            , FlowNode nextNode, FlowParams flowUser) {
        // TODO min 后续抽取用户角色
        FlowInsHis insHis = new FlowInsHis();
        insHis.setId(IdUtils.snowflakeId());
        insHis.setInstanceId(instance.getId());
        insHis.setNodeFrom(instance.getNodeCode());
        insHis.setNodeFromName(instance.getNodeName());
        insHis.setNodeTo(nextNode.getNodeCode());
        insHis.setNodeToName(nextNode.getNodeName());
        insHis.setConditionValue(conditionValue);
        insHis.setMessage(message);
        insHis.setCreateTime(new Date());
        insHis.setCreateBy(flowUser.getCreateBy());
        insHis.setUserName(flowUser.getNickName());
        return insHis;
    }

    /**
     * 设置流程实例对象
     * @param startNode
     * @param businessId
     * @return
     */
    private FlowInstance setStartInstance(FlowNode startNode, String businessId
            , FlowParams flowUser) {
        // TODO min 后续抽取用户角色
        FlowInstance instance = new FlowInstance();
        Date now = new Date();
        Long id = IdUtils.snowflakeId();
        instance.setId(id);
        instance.setDefinitionId(startNode.getDefinitionId());
        instance.setBusinessId(businessId);
        // 关联业务id,起始后面可以不用到业务id,传业务id目前来看只是为了批量创建流程的时候能创建出有区别化的流程,也是为了后期需要用到businessId。
        instance.setNodeCode(startNode.getNodeCode());
        instance.setNodeName(startNode.getNodeName());
        instance.setFlowStatus(startNode.getNodeType());
        instance.setFlowVersion(startNode.getVersion());
        instance.setCreateTime(now);
        instance.setUpdateTime(now);
        instance.setCreateBy(flowUser.getCreateBy());
        instance.setUserName(flowUser.getNickName());
        return instance;
    }

    /**
     * 设置流程实例历史记录对象
     * @param startNode
     * @param instance
     * @return
     */
    private FlowInsHis setStartInsHis(FlowNode startNode, FlowInstance instance
            , FlowParams flowUser) {
        // TODO min 后续抽取用户角色
        FlowInsHis insHis = new FlowInsHis();
        insHis.setId(IdUtils.snowflakeId());
        insHis.setInstanceId(instance.getId());
        insHis.setNodeTo(startNode.getNodeCode());
        insHis.setNodeToName(startNode.getNodeName());
        insHis.setCreateTime(new Date());
        insHis.setCreateBy(flowUser.getCreateBy());
        insHis.setUserName(flowUser.getNickName());
        return insHis;
    }

    /**
     * 批量流程校验,如批量处理一批流程,其中有些流程在a状态，有些在b状态，这样就会抛MUL_FROM_STATUS异常。
     * 这种情况调整为根据流程状态进行分批批量跳转就可以了
     *
     * @param instances
     */
    private void checkSameStatus(List<FlowInstance> instances) {
        Map<String, List<FlowInstance>> groupMap = instances.stream().collect(Collectors.groupingBy(t -> t.getDefinitionId() + "_" + t.getNodeCode()));
        if (groupMap.size() > 1) {
            throw new ServiceException(FlowConstant.MUL_FROM_STATUS);
        }
    }

    /**
     * 权限和条件校验
     *
     * @param skips
     * @param conditionValue
     * @return
     */
    private FlowSkip checkAuthAndCondition(FlowInstance instance, List<FlowSkip> skips, String conditionValue
            , FlowParams flowUser) {
        if (skips == null || skips.size() == 0) {
            return null;
        }
        // TODO min 后续抽取用户角色
        List<String> roleList = flowUser.getRoles();
        // AssertUtil.isFalse(roleList == null || roleList.size() == 0, FlowConstant.NOT_ROLE_PERMISSIONS);
        QueryWrapper queryWrapper = QueryWrapper.create().where(flow_node.definition_id.eq(instance.getDefinitionId()))
                .and(flow_node.node_code.eq(instance.getNodeCode()));
        FlowNode node = nodeService.getOne(queryWrapper);
        AssertUtil.isFalse(StringUtils.isNotBlank(node.getRoleCode()) && (CollUtil.isEmpty(roleList)
                || !roleList.contains(node.getRoleCode())), FlowConstant.NULL_ROLE_NODE);

        // 第一个结点不需要过滤
        if (!NodeType.start.getKey().equals(instance.getFlowStatus())) {
            if (StringUtils.isBlank(conditionValue)) {
                skips = skips.stream().filter(t -> StringUtils.isBlank(t.getConditionValue())).collect(Collectors.toList());
            } else {
                skips = skips.stream().filter(t -> conditionValue.equals(t.getConditionValue())).collect(Collectors.toList());
            }
        }
        AssertUtil.isFalse(skips.size() == 0, FlowConstant.NULL_CONDITIONVALUE_NODE);
        // 第一个结点
        return skips.get(0);
    }

    /**
     * 根据流程id+当前流程结点编码获取与之直接关联(其为源结点)的结点。 definitionId:流程id nodeCode:当前流程状态
     * conditionValue:跳转条件,没有填写的话不做校验
     *
     * @param instance
     * @param conditionValue
     * @param flowUser
     * @return
     */
    private FlowNode getNextNode(FlowInstance instance, String conditionValue
            , FlowParams flowUser) {
        AssertUtil.isNull(instance.getDefinitionId(), FlowConstant.NOT_DEFINITION_ID);
        AssertUtil.isBlank(instance.getNodeCode(), FlowConstant.LOST_NODE_CODE);
        QueryWrapper queryWrapper = QueryWrapper.create().where(flow_skip.definition_id.eq(instance.getDefinitionId()))
                .and(flow_skip.now_node_code.eq(instance.getNodeCode()));
        List<FlowSkip> flowSkips = skipService.list(queryWrapper);
        FlowSkip nextSkip = checkAuthAndCondition(instance, flowSkips, conditionValue, flowUser);
        AssertUtil.isFalse(nextSkip == null, FlowConstant.NULL_DEST_NODE);
        QueryWrapper wrapper = QueryWrapper.create().where(flow_node.definition_id.eq(instance.getDefinitionId()))
                .and(flow_node.node_code.eq(nextSkip.getNextNodeCode()));
        List<FlowNode> nodes = nodeService.list(wrapper);
        AssertUtil.isFalse(nodes.size() == 0, FlowConstant.NOT_NODE_DATA);
        AssertUtil.isFalse(nodes.size() > 1, "[" + nextSkip.getNextNodeCode() + "]" + FlowConstant.SAME_NODE_CODE);
        return nodes.get(0);

    }

    /**
     * 有且只能有一个开始结点
     *
     * @param nodes
     * @return
     */
    private FlowNode getFirstNode(List<FlowNode> nodes) {
        for (int i = 0; i < nodes.size(); i++) {
            if (NodeType.start.getKey().equals(nodes.get(i).getNodeType())) {
                return nodes.get(i);
            }
        }
        throw new ServiceException(FlowConstant.LOST_START_NODE);
    }
}
